package yartiss.engine.simulation;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;

import yartiss.engine.simulation.events.ActivateTaskEvent;
import yartiss.engine.simulation.events.CheckDeadlineEvent;
import yartiss.engine.simulation.events.CheckEffectiveDeadlineEvent;
import yartiss.engine.simulation.events.DeadlineMissedEvent;
import yartiss.engine.simulation.events.EnergyFailureEvent;
import yartiss.engine.simulation.events.EventGenerator;
import yartiss.engine.simulation.events.EventVisitorAdapter;
import yartiss.engine.simulation.events.IEventGeneratedListener;
import yartiss.engine.simulation.events.IEventVisitor;
import yartiss.engine.simulation.events.JobExecutionBeginEvent;
import yartiss.engine.simulation.events.JobExecutionEndEvent;
import yartiss.engine.simulation.io.SimulationWriter;
import yartiss.engine.simulation.logger.Logger;
import yartiss.engine.simulation.system.ITask;
import yartiss.engine.simulation.system.SystemMetrics;

public class Simulation {

	private SimulationParameters parameters;
	private TreeSet<Event> eventQueue;
	private Scheduler scheduler;
	private final SimulationStatistics statistics = new SimulationStatistics();
	private String logFile = "simulation_1";
	private SimulationWriter simulationWriter = new SimulationWriter();
	private EventGenerator eventGen;


	/**
	 * @return the simulationParameters
	 */
	public SimulationParameters getSimulationParameters() {
		return parameters;
	}

	/**
	 * @param simulationParameters the simulationParameters to set
	 */
	public void setSimulationParameters(SimulationParameters simulationParameters) {
		this.parameters = simulationParameters;
		Processor [] prcs = new Processor [simulationParameters.getNbProc()];
		for (int i = 0; i < prcs.length; i++) {
			prcs[i] = new Processor(i+1, 20,20);
		}
		scheduler.setProcessors(prcs);
	}

	/**
	 * 
	 * @param simulationParameters
	 */
	public Simulation(SimulationParameters simulationParameters) {
		this();
		setSimulationParameters(simulationParameters);
	}

	/**
	 * @return the statistics
	 */
	public SimulationStatistics getStatistics() {
		return statistics;
	}

	
	/**
	 * 
	 */
	public Simulation() {
		eventGen = new EventGenerator();
		logFile = "simulation.sim";//hashCode();
		scheduler = new Scheduler(eventGen);
		eventQueue = new TreeSet<Event>(new Comparator<Event>() {
			@Override
			public int compare(Event e1, Event e2) {
				int compare = (int) (e1.getDate() - e2.getDate());
				if (compare == 0) {
					
					int i1 = EventGenerator.getAvailableEvents().indexOf(e1.toString());
					int i2 = EventGenerator.getAvailableEvents().indexOf(e2.toString());
					
					assert i1!=-1 && i2!=-1 : "tostring de event doit renvoyer la chaine presente dans eventgenerator";
						
					compare = i1 - i2;
					//compare = e1.getClass().getName().compareTo(e2.getClass().getName());
				}
				if (compare == 0) {
					compare = e1.getTask().getPriority() - e2.getTask().getPriority();
				}
				if(compare == 0){
					compare = e1.getProcId() - e2.getProcId();
				}
				return compare;
			}
		});
	}

	private IEventGeneratedListener listener = new IEventGeneratedListener() {
		@Override
		public void actionPerformed(Event event) {
			eventQueue.add(event);
		}
	};

	/**
	 * 
	 */
	private void createInitialActicationEventSet(){
		eventQueue.clear();
		for (ITask t : parameters.getTaskSet()){
			eventGen.generateEvent("activate_task",-1, t, t.getFirstRelease(), null);
		}
	}


	private long date = 0, oldDate=0;
	private boolean isLogging;

	/**
	 * 
	 */
	public void simulate(){
		Logger.instance().setEnabled(false);
		if(isLogging){
			simulationWriter.open(logFile);
			simulationWriter.writeParameters(parameters);
		}
		eventGen.addEventGeneratedListener(listener);
		createInitialActicationEventSet();
		SimulationEndManager endManager = new SimulationEndManager();

		while (!eventQueue.isEmpty() && !endManager.isFinished && date < parameters.getSimulationEnd()) {

			Event e = eventQueue.pollFirst();
			assert e.getDate() >= date : "back to the future : event " + e + " at time " + date ;

			date = e.getDate();
			if(date <= parameters.getSimulationEnd()){
				fireEvent(e, date);
				Logger.instance().log(e,"");
				if(isLogging)
					simulationWriter.writeEvent(e);

				fireEventFired(e);
				updateStatistics(date, oldDate, e);
				toto(e);
				e.accept(endManager);

				oldDate = date;
			}
			else date = parameters.getSimulationEnd();
			statistics.setLastEvent(e);
		}
		eventGen.removeEventGeneratedListener(listener);
		if(isLogging)
			simulationWriter.close();

		if(!statistics.isFailed()){
			statistics.setFinished(true);
			statistics.setFailureCount(0);
			statistics.setFinishedCount(1);
		}
		statistics.setSimulationDuration( date );

		// update last possible IP
		if(ipBeginDetected){
			long ipBegin = lastEnd != null ? lastEnd.getDate() : 0;
			long length = (date>parameters.getSimulationEnd() ? parameters.getSimulationEnd() : date) - ipBegin;
			if(length>0){
				sumIP += length;
				nbIP++;
			}
		}

		statistics.setBusyPerdiodLength(sumBP);
		statistics.setNbBusyPeriods(nbBP);

		statistics.setIdlePeriodLength(sumIP);
		statistics.setNbIdlePeriods(nbIP);

		statistics.setAverageBusyPeriod(sumBP/nbBP);
		statistics.setAverageIdlePeriod(sumIP/nbIP);
		//System.out.println("BP="+statistics.getBusyPerdiodLength()+"/"+statistics.getNbBusyPeriods()+"="+((double)statistics.getBusyPerdiodLength())/statistics.getNbBusyPeriods());
		//System.out.println("IP="+statistics.getIdlePeriodLength() +"/"+statistics.getNbIdlePeriods()+"="+((double)statistics.getIdlePeriodLength() )/statistics.getNbIdlePeriods());
	}

	//---------------- statistics -------------------------------------
	long nbBP = 0, nbIP = 0;
	double sumBP= 0 ,sumIP= 0;
	long currentBP=0;
	boolean bpEndDetected = false, bpBeginDetected = false,ipBeginDetected=true;

	Event lastBegin = null;
	Event lastEnd = null;
	IEventVisitor visitor = new EventVisitorAdapter() {

		@Override	public void visit(ActivateTaskEvent   e) { 
			long di = e.getDate() + e.getTask().getDeadline();
			long edi = SystemMetrics.effectiveDeadline(e.getTask(), parameters.getTaskSet(), di);
			//if(edi != di)
			eventGen.generateEvent("check_effective_deadline",0,e.getTask(), edi, null);
		}
		@Override	public void visit(EnergyFailureEvent   e) { end(e); }
		@Override	public void visit(JobExecutionEndEvent e) {	end(e);	}
		@Override	public void visit(DeadlineMissedEvent  e) {	end(e);	}
		@Override	public void visit(CheckEffectiveDeadlineEvent e) {  }
		@Override
		public void visit(JobExecutionBeginEvent e) {
			long ipBegin = lastEnd != null ? lastEnd.getDate() : 0;
			long length = (e.getDate()>parameters.getSimulationEnd() ? parameters.getSimulationEnd() : e.getDate()) - ipBegin;
			if(length>0){
				sumIP += length;
				nbIP++;
			}
			ipBeginDetected = false;
			bpBeginDetected = true;
			lastBegin = e;
		}

		private void end(Event e){
			ipBeginDetected = true;
			bpEndDetected = true;
			bpBeginDetected = false;
			if(lastBegin != null)
				currentBP += (e.getDate() - lastBegin.getDate());
			lastEnd = e;
		}

	};
	private void toto(Event e) {
		e.accept(visitor);

		if(e.getDate()!=oldDate){
			if( bpEndDetected && !bpBeginDetected){    // parameters.getTaskSet().getActiveTasks(e.getDate()).isEmpty()
				sumBP += currentBP;
				currentBP=0;
				nbBP++;
			}
			bpEndDetected = false;
			bpBeginDetected = false;
		}
	}


	class SimulationEndManager extends EventVisitorAdapter {
		public boolean isFinished = false;
		@Override
		public void visit(DeadlineMissedEvent e) {
			failureEnd(e);
		}

		@Override
		public void visit(CheckDeadlineEvent e){
			statistics.setDeadlinesCount(statistics.getDeadlinesCount()+1);
		}

		@Override
		public void visit(EnergyFailureEvent e) {
			failureEnd(e);
		}

		private void failureEnd(Event e){
			statistics.setFinished(false);
			statistics.setFailed(true);
			statistics.setFailureCount(1);
			statistics.setFinishedCount(0);
			statistics.setDeadlinesMissedCount(statistics.getDeadlinesMissedCount()+1);
			isFinished = true;
		}
	}

	private void updateStatistics(long date, long  oldDate, Event e) {
		if (parameters.getEnergyProfile().getCurrentEnergyLevel() == parameters.getEnergyProfile().getMaxEnergyLevel() && date != oldDate) {
			statistics.setTimePassedWithEmax(statistics.getTimePassedWithEmax()+1 );
		} 
		else if (parameters.getEnergyProfile().getCurrentEnergyLevel() == parameters.getEnergyProfile().getMinEnergyLevel() && date != oldDate) {
			statistics.setTimePassedWithEmin(statistics.getTimePassedWithEmin()+1);
		}
	}

	public void setLogFile(String file) {
		this.logFile = file;
	}

	public void setLoggingEnabled(boolean islogging){
		this.isLogging = islogging;
	}

	private void fireEvent(Event event, long date){
		event.actionPerformed(date);
		scheduler.schedule(event, parameters.getTaskSet(), parameters.getSchedulingPolicy(), parameters.getEnergyProfile(), date, parameters.isEnergyAware());
	}

	private List<IEventAddedListener> listeners = new ArrayList<IEventAddedListener>();

	public void addEventAddedListerner(IEventAddedListener l){
		if(!listeners.contains(l))
			listeners.add(l);
	}
	
	public void removeEventAddListener(IEventAddedListener l) {
		if(listeners.contains(l))
			listeners.remove(l);
	}

	protected void fireEventFired(Event e){
		for (IEventAddedListener l : listeners) {
			l.perfomredAction(e);
		}
	}

	




}
